package ru.ifmo.thesis.gp;

import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.jgap.BaseGeneticOperator;
import org.jgap.Chromosome;
import org.jgap.Configuration;
import org.jgap.FitnessEvaluator;
import org.jgap.FitnessFunction;
import org.jgap.Gene;
import org.jgap.IChromosome;
import org.jgap.InvalidConfigurationException;
import org.jgap.Population;
import org.jgap.RandomGenerator;
import org.jgap.event.GeneticEvent;
import org.jgap.event.GeneticEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.ifmo.thesis.domain.Job;
import ru.ifmo.thesis.domain.Worker;
import ru.ifmo.thesis.util.Algorithms;

public class FinalSOMutationOperator extends BaseGeneticOperator {

	private static final double distance = 0.01;

	private static final long serialVersionUID = 1L;
	
	private static final Logger myLogger = LoggerFactory.getLogger(FinalSOMutationOperator.class);
	
	private final Writer writer;
	
	public FinalSOMutationOperator(Configuration aConfiguration)
			throws InvalidConfigurationException {
		
		super(aConfiguration);
		
		try {
			boolean append = true;
			writer = new BufferedWriter(
					new OutputStreamWriter(new FileOutputStream("final_so_distrib-5-single.txt", append)));
		} catch (FileNotFoundException e) {
			throw new RuntimeException(e);
		}
	}

	private int[] mutations;
	int evalancheSize = 1;

	@Override
	public void operate(Population aPopulation, List chromosomes) {
		RandomGenerator rg = getConfiguration().getRandomGenerator();
		List<Chromosome> chromoCopy = Algorithms.deepCopyPopulation(
				(List<Chromosome>) aPopulation.getChromosomes());

		int randomChromosomeIndex = rg.nextInt(chromoCopy.size());
//		FitnessEvaluator fitnessEvaluator = getConfiguration().getFitnessEvaluator();
//		FitnessFunction fitnessFunction = getConfiguration().getFitnessFunction();
//		int randomChromosomeIndex = getWorstIndex(chromoCopy, fitnessEvaluator, fitnessFunction);
		chromosomes.add(aPopulation.getChromosome(randomChromosomeIndex));
				
		List<Chromosome> nighebours = findNeigh(randomChromosomeIndex, chromoCopy);
		
		for (Chromosome c: nighebours) {
//			try {
//				chromosomes.add(randomChromosome(c));
//			} catch (InvalidConfigurationException e) {
//				throw new RuntimeException(e);
//			}
//			aPopulation.getChromosomes().remove(c);
			chromosomes.add(mutate(c));
		}
		
		
		try {
			myLogger.debug("Evalanche size = " + nighebours.size());
			writer.write(nighebours.size() + ",");
			writer.flush();
		} catch (IOException e) {
			throw new RuntimeException();
		}
	}

	private int getWorstIndex(List<Chromosome> chromoCopy,
			FitnessEvaluator fEval, FitnessFunction fFunc) {
		int worstIndividualIndex = 0;
		Chromosome worst = null;
		double worstFitness = 0;
		int i = 0;
		for (Chromosome c : chromoCopy) {
			if (worst == null) {
				worst = c;
				worstFitness = fFunc.getFitnessValue(worst);
				worstIndividualIndex = i;
			} else {
				double currentFitness = fFunc.getFitnessValue(c);
				if (fEval.isFitter(worstFitness, currentFitness)) {
					worst = c;
					worstFitness = fFunc.getFitnessValue(worst);
					worstIndividualIndex = i;
				}
			}
			i++;
		}
		return worstIndividualIndex;
	}

	private Object randomChromosome(Chromosome c) throws InvalidConfigurationException {
		List<Job> jobs = new ArrayList<Job>();
		List<Worker> workers = new ArrayList<Worker>();
		
		for (Gene g : c.getGenes()) {
			WorkPlanGene wpg = (WorkPlanGene) g;
			workers.add(wpg.getAssociatedWorker());
			jobs.addAll((List<Job>)wpg.getAllele());
		}
		
		return createRandomChromosome(getConfiguration(), workers, jobs);
	}
	
	private IChromosome createRandomChromosome(Configuration conf,
			List<Worker> workers, List<Job> jobs) throws InvalidConfigurationException {
		
		Gene[] myGenes = new Gene[workers.size()];

		for (int i = 0; i < workers.size(); i++) {
			myGenes[i] = new WorkPlanGene(conf, workers.get(i));
		}

		List<Job> nJobs = new ArrayList<Job>(jobs);
		
		Collections.shuffle(nJobs);
		for (Job j : nJobs) {
			int rndWorkerId = getConfiguration().getRandomGenerator().nextInt(myGenes.length);
			
			@SuppressWarnings("unchecked")
			ArrayList<Job> allele = (ArrayList<Job>) myGenes[rndWorkerId].getAllele();

			allele.add(j);
		}
		
		return new Chromosome(conf, myGenes);
	}



	private List<Chromosome> findNeigh(int randomChromosomeIndex, List<Chromosome> chromoCopy) {
		FitnessEvaluator fitnessEvaluator = getConfiguration().getFitnessEvaluator();
		FitnessFunction fitnessFunction = getConfiguration().getFitnessFunction();
		
		double best = getBestValue(chromoCopy, fitnessEvaluator, fitnessFunction);
		double worst = getWorstValue(chromoCopy, fitnessEvaluator, fitnessFunction);
		
		double seedFit = fitnessFunction.getFitnessValue(chromoCopy.get(randomChromosomeIndex));
		double seedNormalizedFitness = getNormalizedFitness(seedFit, best, worst);
		
		List<Chromosome> result = new ArrayList<Chromosome>();
		
		for (Chromosome c : chromoCopy) {
			double chromoFit = fitnessFunction.getFitnessValue(c);
			double normalizedChromoFit = getNormalizedFitness(chromoFit, best, worst);
			
			if (Math.abs(normalizedChromoFit - seedNormalizedFitness) < distance) {
				result.add(c);
			}
		}
		
		return result;
		
	}

	private double getNormalizedFitness(double seedFit, double best,
			double worst) {
		return (seedFit - worst) / (best - worst);
	}


	private double getWorstValue(List<Chromosome> chromoCopy,
			FitnessEvaluator fEval, FitnessFunction fFunc) {
		int worstIndividualIndex = 0;
		Chromosome worst = null;
		double worstFitness = 0;
		int i = 0;
		for (Chromosome c : chromoCopy) {
			if (worst == null) {
				worst = c;
				worstFitness = fFunc.getFitnessValue(worst);
				worstIndividualIndex = i;
			} else {
				double currentFitness = fFunc.getFitnessValue(c);
				if (fEval.isFitter(worstFitness, currentFitness)) {
					worst = c;
					worstFitness = fFunc.getFitnessValue(worst);
					worstIndividualIndex = i;
				}
			}
			i++;
		}
		return worstFitness;
	}

	private double getBestValue(List<Chromosome> chromoCopy,
			final FitnessEvaluator fEval, FitnessFunction fFunc) {
		return getWorstValue(chromoCopy, new FitnessEvaluator() {

			@Override
			public boolean isFitter(double a_fitness_value1,
					double a_fitness_value2) {
				return !fEval.isFitter(a_fitness_value1, a_fitness_value2);
			}

			@Override
			public boolean isFitter(IChromosome a_chrom1, IChromosome a_chrom2) {
				return !isFitter(a_chrom1, a_chrom2);
			}
			
		}, fFunc);
	}

	
	private Chromosome mutate(Chromosome c) {
		return applyMutation(c);
	}


	private Chromosome applyMutation(Chromosome c) {
		Gene[] gene = c.getGenes();
		
		//select 2 workers
		
		RandomGenerator rg = getConfiguration().getRandomGenerator();
		
		WorkPlanGene first = (WorkPlanGene) gene[rg.nextInt(gene.length)];
		WorkPlanGene second = (WorkPlanGene) gene[rg.nextInt(gene.length)];
		
		ArrayList<Job> firstAllele = (ArrayList) first.getAllele();
		ArrayList<Job> secondAllele = (ArrayList) second.getAllele();
		
		//choose between swaping and moving
		if (rg.nextBoolean() && !(firstAllele.isEmpty() || secondAllele.isEmpty())) {
			swapping(rg, firstAllele, secondAllele);
		} else {
			moving(rg, firstAllele, secondAllele);
		}
		
		first.setAllele(firstAllele);
		second.setAllele(secondAllele);
		
		return c;
	}


	private void moving(RandomGenerator rg, ArrayList<Job> firstAllele,
			ArrayList<Job> secondAllele) {
		if (firstAllele.isEmpty()) {
			if (!secondAllele.isEmpty()) {
				int movingIndex = rg.nextInt(secondAllele.size());
				firstAllele.add(secondAllele.remove(movingIndex));
			}
		} else {
			int movingIndex = rg.nextInt(firstAllele.size());
			Job value = firstAllele.remove(movingIndex);
			
			if (secondAllele.isEmpty()) {
				secondAllele.add(value);
			} else {
				int insertingIndex = rg.nextInt(secondAllele.size());
				secondAllele.add(insertingIndex, value);
			}
		}
	}


	private void swapping(RandomGenerator rg, ArrayList<Job> firstAllele,
			ArrayList<Job> secondAllele) {
		int swapIndex1 = rg.nextInt(firstAllele.size());
		int swapIndex2 = rg.nextInt(secondAllele.size());
		
		Job tmp = firstAllele.get(swapIndex1);
		firstAllele.set(swapIndex1, secondAllele.get(swapIndex2));
		secondAllele.set(swapIndex2, tmp);
	}


	@Override
	public int compareTo(Object o) {
		return 0;
	}
}
